package http.bio.thread;

import entity.http.HttpRequestEntity;
import entity.http.HttpResponseEntity;
import exception.MinusOneCountException;
import utils.Consts;
import utils.HttpUtils;
import utils.SocketConnectUtils;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class SingleSocketRedirectThread implements Runnable {
    private Socket originalSocket;
    private Socket redirectSocket;

    private String currentTargetHost = "";
    private boolean isHTTPS;
    private boolean isRedirect;
    private boolean isChunked;

    public SingleSocketRedirectThread(Socket socket){
        this.originalSocket = socket;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " start");
        redirectSocket = null;
        InputStream oriInput = null;
        OutputStream oriOutput = null;
        InputStream redirectInput = null;
        OutputStream redirectOutput = null;

        HttpRequestEntity requestEntity = null;
        HttpResponseEntity responseEntity = null;

        isHTTPS = false;
        currentTargetHost = Consts.getProp("redirectHost");
        isRedirect = false;
        isChunked = false;

        try{
            oriInput = originalSocket.getInputStream();
            oriOutput = originalSocket.getOutputStream();

            /**循环里调用originalSocket的write偶尔会抛出SocketException:Software caused connection abort: socket write error
             * 这里通过抓包看的时候不知道为啥浏览器主动发送FIN包关闭了连接，而循环里还继续向浏览器发送数据，导致浏览器发送了一个RST包
             * 然后循环里再次发送数据的时候就出错了
             * https://blog.csdn.net/lemon89/article/details/84640336
             * 找不到出错的原因，不影响效果就先不处理了
             *
             * 是由于处理重定向的过程中忘了重置isRedirect导致一直没法进入读取空请求报文跳出循环的代码段中了
             */

            /**
             * 文件分段下载由出现了一个问题，第一次浏览器发了个range：0-的请求，返回一个Content-Range：0-598279/598280，Content-Length：598280的包
             * 然后浏览器又发了一个Range:299140-的包，此时上一个请求的TCP连接由于读取到空的请求已经断开了连接，第二个请求是新的一条连接，不知道是不是因为这个原因导致下载失败
             *
             * 还发现了一个问题，Response报文里，明明body长度有50w+，但是实际上只有65535长度的数据，剩下的全是0，不知道是不是inputStream的read方法有问题
             *
             * 草，忘了还有tcp半包和粘包的问题，read(byte[])方法不会读取填充数据到byte[]满为止，应该是一个tcp包读完就直接返回了，所以从wireshark里看到的http报文里
             * 只有65535大小的body是有数据的，剩下的全是空的
             */

            /**
             * 有一个大头的问题，线程在完成一次页面的请求之后，会阻塞在read处，然后下一次请求到来的话read方法会直接报错
             * 要不搞个守护线程记录线程状态，卡在读取请求的话就直接interrupt试试
             */

            while(checkAlive(originalSocket, redirectSocket)){
                if(!isRedirect && !isChunked) {
                    requestEntity = HttpUtils.getHttpRequestEntity(oriInput);
                    if(requestEntity == null){
                        System.out.println("Read Null HttpRequestEntity, close connection");
                        break;
                    }
                    HttpUtils.requestEntityDecorator(requestEntity, currentTargetHost);
                    System.out.println(Thread.currentThread().getName() + " Received Request:\r\n" + requestEntity.toString() + "\r\n");
                }

                //如果redirectSocket为null的话，建立连接
                if(redirectSocket == null){
                    redirectSocket = SocketConnectUtils.getSocketByHost(currentTargetHost, isHTTPS);
                    redirectInput = redirectSocket.getInputStream();
                    redirectOutput = redirectSocket.getOutputStream();
                }

                //处于分块传输的过程中
                if(isChunked){
                    try{
                        byte[] chunkedData = HttpUtils.readLine(redirectInput);

                        //如果这一行的内容开头三个char是"0\r\n"，那么表示读到了分块的最后一块,输出这行0还有下一行数据就完了
                        //草，怎么还有\r0\r\n这种格式的
                        if((chunkedData.length >= 3 && "0\r\n".equals(new String(chunkedData, 0, 3))) ||
                                (chunkedData.length >= 4 && "\n0\r\n".equals(new String(chunkedData, 0, 4))) ) {
                            oriOutput.write(chunkedData);
                            oriOutput.write(HttpUtils.readLine(redirectInput));
                            isChunked = false;
                        }else{
                            oriOutput.write(chunkedData);
                        }
                    }catch (MinusOneCountException e){
                        isChunked = false;
                    }

                }else {
                    //读取请求报文并转发到重定向socket中
                    HttpUtils.messageTransport(oriInput, redirectOutput, requestEntity);
                    responseEntity = HttpUtils.getHttpResponseEntity(redirectInput);
                    if(responseEntity == null){
                        break;
                    }
                    System.out.println(Thread.currentThread().getName() + " Received Response:\r\n" + responseEntity.toString() + "\r\n");
                    //如果返回的Response报文的状态吗为3xx，则处理重定向
                    if (HttpUtils.isRedirectResponse(responseEntity) && responseEntity.getHeaders().containsKey("Location")){
                        String forwardUrl = responseEntity.getHeaders().get("Location");

                        if("/".equals(forwardUrl.substring(0, 1))){
                            //同域的重定向让浏览器处理吧
                            //好像也不需要做啥。。。。
                        }
                        else if(forwardUrl.indexOf("://") != -1){
                            //跨域的重定向，把目标转发地址设置为Response报文中的新地址并关闭原来的redirectSocket，不改变request报文，重新吧报文发送到新地址
                            String prefix = forwardUrl.substring(0, forwardUrl.indexOf("://"));
                            forwardUrl = forwardUrl.substring(forwardUrl.indexOf("://") + 3);
                            String suffix = forwardUrl.substring(0, forwardUrl.indexOf("/"));

                            currentTargetHost = suffix;

                            if(prefix.equals("https")){
                                isHTTPS = true;
                            }

                            isRedirect = true;
                            redirectSocket.close();
                            redirectSocket = null;

                            continue;
                        }

                    }else{
                        isRedirect = false;
                    }

                    if("chunked".equals(responseEntity.getHeaders().get("Transfer-Encoding"))){
                        isChunked = true;
                    }

                    HttpUtils.messageTransport(redirectInput, oriOutput, responseEntity);

                    if("close".equals(responseEntity.getHeaders().get("Connection"))){
                        System.out.println("Response with Connection Close");
                        break;
                    }
                }

            }
            oriInput.close();
            oriOutput.close();
            originalSocket.close();
            if(redirectSocket != null){
                redirectInput.close();
                redirectOutput.close();
                redirectSocket.close();
            }

        }catch (Exception e){
            System.out.println(Thread.currentThread().getName() + " exception catch");
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " finished");
    }

    private boolean checkAlive(Socket originalSocket, Socket redirectSocket){
        return originalSocket.isConnected() && redirectSocket == null ? true : redirectSocket.isConnected();
    }
}
